#pragma once
#include "thread.hpp"
#include "lockGuard.hpp"
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include "log.hpp"

/* 封装线程池：本质是一个生产消费模型 */

const int g_defualt_num = 3; // 定义默认的线程池个数！

template <class T>
class ThreadPool
{
public:
    pthread_mutex_t *getMutex()
    { // 获取锁
        return &_mtx;
    }
    bool taskQueueIsEmpty()
    { // 判断任务队列是否为空
        return _task_queue.empty();
    }
    void waitCond()
    { // 进入线程等待
        pthread_cond_wait(&_cond, &_mtx);
    }
    T getTask()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

public:
    ThreadPool(int defualt_num = g_defualt_num)
        : _num(defualt_num)
    /**
     * 注意：为什么下面可以传递 this 指针？
     * 实际：只要到达函数体部分！函数体内执行的内容都是一个赋值过程！
     * 即：空间早已申请了！因此可以使用 this 指针了！
     */
    {
        pthread_mutex_init(&_mtx, nullptr); // 初始化锁
        pthread_cond_init(&_cond, nullptr); // 初始化条件变量
        // 构造线程
        for (int i = 1; i <= _num; i++)
        {
            // _threads.push_back(new Thread(i, routine, nullptr));
            _threads.push_back(new Thread(i, routine, this)); // 解决静态方法访问类属性问题
        }
    }

    // 线程池的整体启动！run()
    void run()
    {
        for (auto &iter : _threads)
        {
            iter->start();
            // std::cout << iter->name() << " 线程启动！" << std::endl;
            logMessage(NORMAL, "%s%s", iter->name().c_str(), " 线程启动！");
        }
    }

    // 线程执行的任务：目前是有问题的！
    // void *routine(void* args)
    // 以上写法存在问题，此函数实际包含两个参数，第一个隐藏的 this 指针参数！可能会导致编译无法通过
    /**
     * routine函数必定会被所有线程所执行！
     * 本质是一个消费的过程！
     * 消费就必定要访问：任务队列！但是：任务队列是一个类内的成员/方法
     * C++知识点：如果类内的成员方法被 static 修饰时，该方法只能使用静态成员属性或调用静态方法；
     *            无法使用类内的成员属性/成员方法！
     * 解决办法：想办法直接拿到线程池对象，此时就需要回到线程构造的参数传递处，直接传递对象 this
     */
    // 修改：
    static void *routine(void *args)
    {
        ThreadData *td = (ThreadData *)args;
        ThreadPool<T> *tp = (ThreadPool<T> *)td->_args;
        while (true)
        {
            // std::cout << "我是一个线程，我的名字是：" << td->_name << std::endl;
            // sleep(1);
            T task;
            { // 这个代码块是为了加锁范围只涉及领取任务，不涉及处理任务！
                // 加锁
                lockGuard lockguard(tp->getMutex());
                // 如果：任务队列为空！则进入等待！（在条件变量下等待）
                // 任务检测
                while (tp->taskQueueIsEmpty())
                {
                    tp->waitCond();
                }
                // 有任务，则获取任务
                task = tp->getTask(); // 任务队列是共享的！该步骤就是把任务私有化！用于处理！
                // 解锁
            }
            // 处理任务
            task(td->_name);
        }
    }

    // 任务分配给线程：pushTask()
    // 任务入队：相当于生产者的生产过程！
    void pushTask(const T &task)
    {
        // 入队：注意安全问题：加锁解决
        // 加锁
        lockGuard lockguard(&_mtx); // 加锁目的？任务队列时共享资源！需要进行保护！
        _task_queue.push(task);
        // 多线程下：未来可能出现多个线程来拿任务！任务放入的过程中可能同时有拿任务和检测
        // 队列在被同时，入队和出队时应有保护措施！
        // 到此为止，任务入队已安全！任务生产完成！

        // 但是，如果出现没有任务时，线程必定会发生检测！且若资源未就绪！
        // 线程会被挂起（休眠/等待）！此时，需要对挂起的线程进行必要的唤醒！
        pthread_cond_signal(&_cond); // 线程唤醒：如果所有线程都在工作，唤醒一下并不影响！
    }

    // 临时方法
    // void joins()
    // {
    //     for (auto &iter : _threads)
    //     {
    //         iter->join(); // 线程等待（释放资源）
    //         //    delete iter;
    //     }
    // }
    // 临时方法
    // void show()
    // {
    //     std::cout << "测试是否可以在线程构造时传递 this 指针并访问类内方法！" << std::endl;
    // }

    ~ThreadPool()
    {
        // 销毁
        for (auto &iter : _threads)
        {
            iter->join(); // 线程等待（释放资源）
            delete iter;
        }
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_cond);
    }

private:
    std::vector<Thread *> _threads; // 线程池容器【保存线程对象地址】
    int _num;                       // 记录线程池内线程个数
    std::queue<T> _task_queue;      // 任务队列
    pthread_mutex_t _mtx;           // 定义锁
    pthread_cond_t _cond;           // 定义条件变量

    // 方案二：任务队列设计思路
    /**
     * 两个队列：q1，q2
     * 两个指针指向队列
     * p_queue->queue1
     * c_queue->queue2
     * p_queue -> 生产一批任务之后，swap(p_queue,c_queue),唤醒所有线程/一个线程
     * 当消费者处理完毕的时候，你也可以进行swap(p_queue,c_queue)
     * 因为我们生产和消费用的是不同的队列，未来我们要进行资源的处理的时候，仅仅是指针
    */
};